package nachos.userprog;

import nachos.machine.*;
import nachos.threads.*;
import nachos.userprog.*;

import java.io.EOFException;
import java.util.HashMap;
import java.util.Map;

/**
 * Encapsulates the state of a user process that is not contained in its user
 * thread (or threads). This includes its address translation state, a file
 * table, and information about the program being executed.
 * 
 * <p>
 * This class is extended by other classes to support additional functionality
 * (such as additional syscalls).
 * 
 * @see nachos.vm.VMProcess
 * @see nachos.network.NetProcess
 */
//Reference: 1) https://github.com/lqhl/lqhl-acm-nachos/tree/master/src/nachos/filesys
//2) https://github.com/thinkhy/CS162/wiki/Note-for-Project-1#wiki-task-i-5-5-lines-join
//3) https://github.com/joey2005/nachos-sjtu

public class UserProcess {
	
	// the wrapper class for handling filedescriptor
	public class DescriptorManager {
		private String[] descriptor = new String[maxFileDescriptorNum];
		
		// record the pointer for each file seeker
		private int[] fileReadPos = new int[maxFileDescriptorNum]; // pos for read
		private int[] fileWritePos = new int[maxFileDescriptorNum]; // pos for write

		public DescriptorManager(){
			descriptor[0] = "stdin";
			descriptor[1] = "stdout";
			for(int i=2; i<maxFileDescriptorNum; i++)
				descriptor[i] = null;
		}
		
		public int getFileReadPos(int fd){
			return fileReadPos[fd];
		}
		
		public void setFileReadPos(int fd, int pos){
			fileReadPos[fd] = pos;
		}
		
		public int getFileWritePos(int fd){
			return fileWritePos[fd];
		}
		
		public void setFileWritePos(int fd, int pos){
			fileWritePos[fd] = pos;
		}
		
		// check if descriptor list has valid file name
		public boolean hasFile(int fd){
			return descriptor[fd] != null;
		}
		
		// remove openfile from list given descriptor number
		public void removeFile(int fd){
			removeFile(descriptor[fd]);
		}
		
		// remove openfile from openlist given filename
		public void removeFile (String filename){
			if(filename == null || !opened.containsKey(filename))
				return;
			
			// decrement the # of files that current process holds
			opened.put(filename, opened.get(filename)-1);
			
			// if current process holds 0 file and it is in deleted list ready for deletion, remove from filesystem
			if(opened.get(filename) == 0){
				opened.remove(filename);
				if(deleted.containsKey(filename)){
					Machine.stubFileSystem().remove(filename);
					deleted.remove(filename);
				}
			}
		}
		
		// add file to openlist
		public void addFile(String filename){
			if(filename == null)
				Lib.debug(dbgProcess, "DescriptorManager -- invalid file name");
			if(!opened.containsKey(filename))
				opened.put(filename, 1);
			else{
				opened.put(filename, opened.get(filename)+1);
			}
		}
		
		// insert open file to available spot in list, return the index of descriptor, error return -1
		public int addFileToFileDesciptorList(OpenFile file, boolean isOpen){
			int fd = -1;
			for (int i = 2; i < maxFileDescriptorNum; i++){
	            if (!hasFile(i)){
	            	descriptor[i] = file.getName();
	                fileReadPos[i] = 0;
	                fileWritePos[i] = 0;
					fd = i;
					// if we handle open we need to add it to openlist
					if(isOpen)
						addFile(file.getName());
					file.close();
	                break;
	            }
	        }
			
			if(fd == -1){
				file.close();
			}
			
			return fd;
		}
	}
	
	// ID of process (for proj3)
	protected int processID;
	
	// each time incrementing by 1 for PID
	protected static int processNumber = 0;
	
	// the value of returned process
	public int status;
	
	// is current process root process?
	public boolean rootProcess = false;
	
	// lock for join operation
	protected Semaphore finished;
	
	// indicator for exit status
	protected boolean exitNormally = true;
	
	private DescriptorManager descriptorManager;
	
	// each process has list of childprocess [pid and process pair]
	private Map<Integer,UserProcess> childProcesses = new HashMap<Integer, UserProcess>();;
	
	// golbal UserProcess list
	private static Map <Integer,UserProcess> processes = new HashMap<Integer, UserProcess>();
	
	// The files # that are ready to be deleted
	protected static Map<String,Integer> deleted = new HashMap<String,Integer> ();
		
	// The files # that arecurrent open
	protected static Map<String,Integer> opened = new HashMap<String,Integer> ();
	
	protected static final int maxFileDescriptorNum = 16;
		
	// reserved for stdIn and stdout
	protected OpenFile stdIn;
	
	protected OpenFile stdOut;
	
	/**
	 * Allocate a new process.
	 */
	
	public UserProcess() {
		processID = processNumber++;

		pageTable = new TranslationEntry[Machine.processor().getNumPhysPages()];
		processes.put(processID, this);
	
		finished = new Semaphore(0);
		descriptorManager = new DescriptorManager();
		
		// allocate stdin and stdout
		stdIn = UserKernel.console.openForReading();
		stdOut = UserKernel.console.openForWriting();
	}

	/**
	 * Allocate and return a new process of the correct class. The class name is
	 * specified by the <tt>nachos.conf</tt> key
	 * <tt>Kernel.processClassName</tt>.
	 * 
	 * @return a new process of the correct class.
	 */
	public static UserProcess newUserProcess() {
		return (UserProcess) Lib.constructObject(Machine.getProcessClassName());
	}

	/**
	 * Execute the specified program with the specified arguments. Attempts to
	 * load the program, and then forks a thread to run it.
	 * 
	 * @param name the name of the file containing the executable.
	 * @param args the arguments to pass to the executable.
	 * @return <tt>true</tt> if the program was successfully executed.
	 */
	public boolean execute(String name, String[] args) {
		if (!load(name, args))
			return false;
		
		new UThread(this).setName(name).fork();

		return true;
	}

	/**
	 * Save the state of this process in preparation for a context switch.
	 * Called by <tt>UThread.saveState()</tt>.
	 */
	public void saveState() {
	}

	/**
	 * Restore the state of this process after a context switch. Called by
	 * <tt>UThread.restoreState()</tt>.
	 */
	public void restoreState() {
		Machine.processor().setPageTable(pageTable);
	}

	public String readVirtualMemoryString(int vaddr, int maxLength) {
        Lib.assertTrue(maxLength >= 0);

	byte[] bytes = new byte[maxLength+1];

        int bytesRead = readVirtualMemory(vaddr, bytes);

	for (int length=0; length<bytesRead; length++) {
	    if (bytes[length] == 0)
                return new String(bytes, 0, length);
            }

        return null;
    }

    /**
     * Transfer data from this process's virtual memory to all of the specified
     * array. Same as <tt>readVirtualMemory(vaddr, data, 0, data.length)</tt>.
     *
     * @param	vaddr	the first byte of virtual memory to read.
     * @param	data	the array where the data will be stored.
     * @return	the number of bytes successfully transferred.
     */
    public int readVirtualMemory(int vaddr, byte[] data) {
        return readVirtualMemory(vaddr, data, 0, data.length);
    }

    /**
     * Transfer data from this process's virtual memory to the specified array.
     * This method handles address translation details. This method must
     * <i>not</i> destroy the current process if an error occurs, but instead
     * should return the number of bytes successfully copied (or zero if no
     * data could be copied).
     *
     * @param	vaddr	the first byte of virtual memory to read.
     * @param	data	the array where the data will be stored.
     * @param	offset	the first byte to write in the array.
     * @param	length	the number of bytes to transfer from virtual memory to
     *			the array.
     * @return	the number of bytes successfully transferred.
     */
    public int getPhysAddrForDebug(int vaddr)
    {
    	// [Yungho] for debugging only
    	int thisPage = Processor.pageFromAddress(vaddr);
    	return Processor.makeAddress(pageTable[thisPage].ppn, 0);
    }
    
    public int readVirtualMemory(int vaddr, byte[] data, int offset, int length) {
		Lib.assertTrue(offset >= 0 && length >= 0 && offset+length <= data.length);

        byte[] memory = Machine.processor().getMemory();
        
        int theLastByteAddress = Machine.processor().makeAddress(numPages-1, pageSize-1);

		// [Yungho] for now, just assume that virtual addresses equal physical addresses
		if (vaddr < 0 || vaddr >= theLastByteAddress)
			return 0;

		int amount = Math.min(length, memory.length-vaddr);
		
		// changed. the system copy the memory cell by one by one, not using the array copy.
		for(int i = 0; i < amount; i++)
        {
            int thisPage = Processor.pageFromAddress(vaddr+i);
            int voffset = Processor.offsetFromAddress(vaddr+i);

            int paddr = Processor.makeAddress(pageTable[thisPage].ppn, voffset);

            // [Yungho] check that the physical memory is not invaded
            if (paddr < 0 || paddr >= memory.length)
                return i;	// they must always return the number of bytes transferred (even if that number is zero). 

            // [Yungho] Copy the cell one by one
            data[i+offset] = memory[paddr];
        }

        return amount;
    }

    /**
     * Transfer all data from the specified array to this process's virtual
     * memory.
     * Same as <tt>writeVirtualMemory(vaddr, data, 0, data.length)</tt>.
     *
     * @param	vaddr	the first byte of virtual memory to write.
     * @param	data	the array containing the data to transfer.
     * @return	the number of bytes successfully transferred.
     */
    public int writeVirtualMemory(int vaddr, byte[] data) {
        return writeVirtualMemory(vaddr, data, 0, data.length);
    }

    /**
     * Transfer data from the specified array to this process's virtual memory.
     * This method handles address translation details. This method must
     * <i>not</i> destroy the current process if an error occurs, but instead
     * should return the number of bytes successfully copied (or zero if no
     * data could be copied).
     *
     * @param	vaddr	the first byte of virtual memory to write.
     * @param	data	the array containing the data to transfer.
     * @param	offset	the first byte to transfer from the array.
     * @param	length	the number of bytes to transfer from the array to
     *			virtual memory.
     * @return	the number of bytes successfully transferred.
     */
    public int writeVirtualMemory(int vaddr, byte[] data, int offset, int length) {
    	//Lib.assertTrue(offset >= 0 && length >= 0 && offset+length <= data.length);
    	if(offset < 0 && length < 0 && offset+length < data.length)
		{
			return 0;
		}

        int theLastByteAddress = Machine.processor().makeAddress(numPages-1, pageSize-1);

    	// [Yungho] for now, just assume that virtual addresses equal physical addresses
    	if (vaddr < 0 || vaddr >= theLastByteAddress)
    		return 0;

        byte[] memory = Machine.processor().getMemory();

		int amount = Math.min(length, memory.length-vaddr);

		// changed. the system copy the memory cell by one by one, not using the array copy.		
		for(int i = 0; i < amount; i++)
		{
        	int thisPage = Processor.pageFromAddress(vaddr+i);
	        int voffset = Processor.offsetFromAddress(vaddr+i);

    	    int paddr = Processor.makeAddress(pageTable[thisPage].ppn, voffset);

    	    // [Yungho] check that the physical memory is not invaded
			if (paddr < 0 || paddr >= memory.length)
            	return i;	// they must always return the number of bytes transferred (even if that number is zero). 

			// [Yungho] Copy the cell one by one
			memory[paddr] = data[i+offset];
		}

        return amount;
    }

	/**
	 * Load the executable with the specified name into this process, and
	 * prepare to pass it the specified arguments. Opens the executable, reads
	 * its header information, and copies sections and arguments into this
	 * process's virtual memory.
	 * 
	 * @param name the name of the file containing the executable.
	 * @param args the arguments to pass to the executable.
	 * @return <tt>true</tt> if the executable was successfully loaded.
	 */
	private boolean load(String name, String[] args) {
		Lib.debug(dbgProcess, "UserProcess.load(\"" + name + "\")");

		OpenFile executable = UserKernel.fileSystem.open(name, false);
		if (executable == null) {
			Lib.debug(dbgProcess, "\topen failed");
			return false;
		}

		try {
			coff = new Coff(executable);
		}
		catch (EOFException e) {
			executable.close();
			Lib.debug(dbgProcess, "\tcoff load failed");
			return false;
		}

		// make sure the sections are contiguous and start at page 0
		numPages = 0;
		for (int s = 0; s < coff.getNumSections(); s++) {
			CoffSection section = coff.getSection(s);
			if (section.getFirstVPN() != numPages) {
				coff.close();
				Lib.debug(dbgProcess, "\tfragmented executable");
				return false;
			}
			numPages += section.getLength();
		}

		// make sure the argv array will fit in one page
		byte[][] argv = new byte[args.length][];
		int argsSize = 0;
		for (int i = 0; i < args.length; i++) {
			argv[i] = args[i].getBytes();
			// 4 bytes for argv[] pointer; then string plus one for null byte
			argsSize += 4 + argv[i].length + 1;
		}
		if (argsSize > pageSize) {
			coff.close();
			Lib.debug(dbgProcess, "\targuments too long");
			return false;
		}

		// program counter initially points at the program entry point
		initialPC = coff.getEntryPoint();

		// next comes the stack; stack pointer initially points to top of it
		numPages += stackPages;
		initialSP = numPages * pageSize;

		// and finally reserve 1 page for arguments
		numPages++;

		if (!loadSections())
		{
			return false;
		}

		// store arguments in last page
		int entryOffset = (numPages - 1) * pageSize;
		int stringOffset = entryOffset + args.length * 4;

		this.argc = args.length;
		this.argv = entryOffset;		

		for (int i = 0; i < argv.length; i++) {
			byte[] stringOffsetBytes = Lib.bytesFromInt(stringOffset);
			Lib.assertTrue(writeVirtualMemory(entryOffset, stringOffsetBytes) == 4);
			entryOffset += 4;
			Lib.assertTrue(writeVirtualMemory(stringOffset, argv[i]) == argv[i].length);
			stringOffset += argv[i].length;
			Lib.assertTrue(writeVirtualMemory(stringOffset, new byte[] { 0 }) == 1);
			stringOffset += 1;
		}

		return true;
	}

	/**
	 * Allocates memory for this process, and loads the COFF sections into
	 * memory. If this returns successfully, the process will definitely be run
	 * (this is the last step in process initialization that can fail).
	 * 
	 * @return <tt>true</tt> if the sections were successfully loaded.
	 */
	protected static int getRemainedNumPhysPages()
	{
		Lib.debug(dbgPeter, "[+][Peter] globalPageTable remained: " + UserKernel.globalPageTable.size());
		
		return UserKernel.globalPageTable.size();
	}
	protected boolean allocatePhysPages(int numPages)
	{
		Lib.debug(dbgPeter, "[+][Peter] allocatePhysPages() called");
		
		lock.acquire();		// [Yungho] we must use a lock
		
		// [Yungho] moved below routines from loadSections(),
		// and modified the condition (Machine.processor().getNumPhysPages() -> globalPageTable.size())
		if (numPages > getRemainedNumPhysPages()) {
			Lib.debug(dbgProcess, "\tinsufficient physical memory");
			Lib.debug(dbgPeter, "[+][Peter] insufficient physical memory");

			coff.close();
			
			lock.release();
			return false;
		}
		else	// [Yungho] copied below routines from Constructor, and modified to use globalPageTable variable
		{
			// [Yungho] If some allocation algorithm is needed, make thos here!
		    for(int i = 0; i < numPages; i++)
		         pageTable[i] = new TranslationEntry(i, UserKernel.globalPageTable.removeFirst(), true, false, false, false);
		    for(int i = numPages; i < Machine.processor().getNumPhysPages(); i++)
		         pageTable[i] = new TranslationEntry(i, 0, false, false, false, false);
		    
		}
		
		lock.release();
		return true;
	}
	protected void deallocatePhysPages(TranslationEntry[] pageTable)
	{
		Lib.debug(dbgPeter, "[+][Peter] deallocatePhysPages() called");
	
		lock.acquire();
		
		for(int i = 0; i < numPages; i++)
        {
            if(pageTable[i].valid)
            {
                int freePage = pageTable[i].ppn;
                UserKernel.globalPageTable.addLast(freePage);

                byte[] memory = Machine.processor().getMemory();

				// blank out used pages
                for(int j = 0; j < pageSize; j++)
                {
                    int paddr = Processor.makeAddress(freePage, j);
                    memory[paddr] = 0;
                }
            }
        }
		// [Yungho] if sorting algorithm is needed, make those at here!
		lock.release();
	}
	
	
	static Lock lock = new Lock();		// [Yungho] lock for allocation

	protected boolean loadSections() {
        if (numPages > Machine.processor().getNumPhysPages()) {
            coff.close();
            Lib.debug(dbgProcess, "\tinsufficient physical memory");
            return false;
        }
	
	    // [Yungho] Allocation
	    boolean ret = allocatePhysPages(numPages);
	    if(ret == false)
	    	return false;	// failed to allocate memory

	    // Section Loading.
		for (int s=0; s<coff.getNumSections(); s++)
		{
			CoffSection section = coff.getSection(s);
	
			Lib.debug(dbgProcess, "\tinitializing " + section.getName() + " section (" + section.getLength() + " pages)");
	
		    for (int i=0; i<section.getLength(); i++)
		    {
		    	int vpn = section.getFirstVPN()+i;
		    	pageTable[vpn].readOnly = section.isReadOnly();
	
		    	section.loadPage(i, pageTable[vpn].ppn);
		    }
		}
	
			coff.close();
	        return true;
    }

	/**
	 * Release any resources allocated by <tt>loadSections()</tt>.
	 */
    protected void unloadSections() {
        deallocatePhysPages(pageTable);
        
        return;
    }


	/**
	 * Initialize the processor's registers in preparation for running the
	 * program loaded into this process. Set the PC register to point at the
	 * start function, set the stack pointer register to point at the top of the
	 * stack, set the A0 and A1 registers to argc and argv, respectively, and
	 * initialize all other registers to 0.
	 */
	public void initRegisters() {
		Processor processor = Machine.processor();

		// by default, everything's 0
		for (int i = 0; i < processor.numUserRegisters; i++)
			processor.writeRegister(i, 0);

		// initialize PC and SP according
		processor.writeRegister(Processor.regPC, initialPC);
		processor.writeRegister(Processor.regSP, initialSP);

		// initialize the first two argument registers to argc and argv
		processor.writeRegister(Processor.regA0, argc);
		processor.writeRegister(Processor.regA1, argv);
	}

	/**
	 * Handle the halt() system call.
	 */
	protected static final int syscallHalt = 0, syscallExit = 1,
			syscallExec = 2, syscallJoin = 3, syscallCreate = 4,
			syscallOpen = 5, syscallRead = 6, syscallWrite = 7,
			syscallClose = 8, syscallUnlink = 9;

	/**
	 * Handle a syscall exception. Called by <tt>handleException()</tt>. The
	 * <i>syscall</i> argument identifies which syscall the user executed:
	 * 
	 * <table>
	 * <tr>
	 * <td>syscall#</td>
	 * <td>syscall prototype</td>
	 * </tr>
	 * <tr>
	 * <td>0</td>
	 * <td><tt>void halt();</tt></td>
	 * </tr>
	 * <tr>
	 * <td>1</td>
	 * <td><tt>void exit(int status);</tt></td>
	 * </tr>
	 * <tr>
	 * <td>2</td>
	 * <td><tt>int  exec(char *name, int argc, char **argv);
     * 								</tt></td>
	 * </tr>
	 * <tr>
	 * <td>3</td>
	 * <td><tt>int  join(int pid, int *status);</tt></td>
	 * </tr>
	 * <tr>
	 * <td>4</td>
	 * <td><tt>int  creat(char *name);</tt></td>
	 * </tr>
	 * <tr>
	 * <td>5</td>
	 * <td><tt>int  open(char *name);</tt></td>
	 * </tr>
	 * <tr>
	 * <td>6</td>
	 * <td><tt>int  read(int fd, char *buffer, int size);
     *								</tt></td>
	 * </tr>
	 * <tr>
	 * <td>7</td>
	 * <td><tt>int  write(int fd, char *buffer, int size);
     *								</tt></td>
	 * </tr>
	 * <tr>
	 * <td>8</td>
	 * <td><tt>int  close(int fd);</tt></td>
	 * </tr>
	 * <tr>
	 * <td>9</td>
	 * <td><tt>int  unlink(char *name);</tt></td>
	 * </tr>
	 * </table>
	 * 
	 * @param syscall
	 *            the syscall number.
	 * @param a0
	 *            the first syscall argument.
	 * @param a1
	 *            the second syscall argument.
	 * @param a2
	 *            the third syscall argument.
	 * @param a3
	 *            the fourth syscall argument.
	 * @return the value to be returned to the user.
	 */
	public int handleSyscall(int syscall, int a0, int a1, int a2, int a3) {
		switch (syscall) {
		case syscallHalt:
			return handleHalt();
		case syscallExit:
			return handleExit(a0);
		case syscallExec:
			return handleExec(a0, a1, a2);
		case syscallJoin:
			return handleJoin(a0, a1);
		case syscallCreate:
			return handleCreate(a0);
		case syscallOpen:
			return handleOpen(a0);
		case syscallRead:
			return handleRead(a0, a1, a2);
		case syscallWrite:
			return handleWrite(a0, a1, a2);
		case syscallClose:
			return handleClose(a0);
		case syscallUnlink:
			return handleUnlink(a0);

		default:
			Lib.debug(dbgProcess, "Unknown syscall " + syscall);
			exitNormally = false;
			handleExit(-1);
		}
		return 0; 
	}

	// need trace the resources, i,e, add hashtable as new variables. 
	protected int handleUnlink(int vaddr){
		String filename = readVirtualMemoryString(vaddr, 256);

		if(filename == null ){
			Lib.debug(dbgProcess, "invalid filename in handleUnlink");
			return -1;
		}
		
		// remove from filesystem if not finding corresponding opened file
		if(!opened.containsKey(filename)){
			Machine.stubFileSystem().remove(filename);
		}
		
		// add to ready deletion list
		else if (!deleted.containsKey(filename)){
			deleted.put(filename, 1);
		}
		
		return 0;
	}

	protected int handleClose(int fileDescriptor){
		if(fileDescriptor < 0 || fileDescriptor >= maxFileDescriptorNum ){
			Lib.debug(dbgProcess, "Invalid handleClose, check file number > 16 || <0 ");
			return -1;
		}
		
		descriptorManager.removeFile(fileDescriptor);
		// reset read pointer and write pointer
		descriptorManager.setFileReadPos(fileDescriptor, 0);
		descriptorManager.setFileWritePos(fileDescriptor, 0);
		return 0;
	}

	protected int handleWrite(int fileDescriptor, int buffer, int count) {
		if(fileDescriptor < 0 || fileDescriptor >= maxFileDescriptorNum || count < 0 || buffer < 0){
			Lib.debug(dbgProcess, "Invalid handleWrite, check file number >= 16 || <0 or file does not match fileDescriptor or address is not valid or buffer size < 0");
			return -1;
		}

		// the buffer for holding write data
		byte[] buffer_for_write = new byte[count];
		
		// in output console
		if(fileDescriptor == 1){
			int stdRead = readVirtualMemory(buffer, buffer_for_write, 0, count);
			Lib.debug(dbgProcess, "handleWrite = " + getPhysAddrForDebug(buffer));
			if(stdRead == -1) 
				return -1;

            int stdWrite = stdOut.write(buffer_for_write, 0, count);
			// check range
            if(stdWrite < count) 
            	return -1;
			return stdWrite;
		}
		
		// posiiton for next write and remove the pointer to next write location
		int pos = descriptorManager.getFileWritePos(fileDescriptor);
		int datatransferred = readVirtualMemory(buffer, buffer_for_write, 0, count);
		Lib.debug(dbgProcess, "handleWrite = " + getPhysAddrForDebug(buffer));
		
		OpenFile curOpenFile = Machine.stubFileSystem().open(descriptorManager.descriptor[fileDescriptor], false );
		curOpenFile.seek(pos);
		
	    int bytesWritten = curOpenFile.write(pos, buffer_for_write, 0, count);
		pos = curOpenFile.tell();
		curOpenFile.close();
		
		if (bytesWritten < count){
			Lib.debug(dbgProcess, "bytesWritten bytes less than count");
            return -1;
        }
		// set the fileDescriptor to corresponding file pointer location
		descriptorManager.setFileWritePos(fileDescriptor, pos);
		return bytesWritten;
	}

	protected int handleRead(int fileDescriptor, int vaddr, int count) {
	        byte[] buffer = new byte[count];
			if(fileDescriptor < 0 || fileDescriptor >= maxFileDescriptorNum)
	            return -1;
			
			if(fileDescriptor == 0){
				
				int stdRead = stdIn.read(buffer, 0, count);
				if(stdRead == -1) return -1;
				
				int stdWritten = writeVirtualMemory(vaddr, buffer);
				Lib.debug(dbgProcess, "handleRead = " + getPhysAddrForDebug(vaddr));
				if(stdWritten < stdRead) return -1;
				
				return stdRead;
			}

	        //file position pending read
	        int pos = descriptorManager.getFileReadPos(fileDescriptor);
	        
	        // continue seeking starting the last visited posiiton
	        OpenFile curOpenFile = Machine.stubFileSystem().open(descriptorManager.descriptor[fileDescriptor], false );
	        curOpenFile.seek(pos);
			
	        int bytesRead = curOpenFile.read(buffer, 0, count);
			pos = curOpenFile.tell();
			curOpenFile.close();

			if(bytesRead == -1) 
				return -1;

	        // adjust position
			int current_pos = descriptorManager.getFileReadPos(fileDescriptor);
			descriptorManager.setFileReadPos(fileDescriptor, current_pos + bytesRead);

	       	int bytesWritten = writeVirtualMemory( vaddr, buffer );
	       	Lib.debug(dbgProcess, "handleRead = " + getPhysAddrForDebug(vaddr));
	        
	       	// do not get enough bytes
	       	if (bytesWritten < bytesRead)
	        	return -1;
	        
	        return bytesRead;
	    }

	protected int handleOpen(int vaddr){
		// nearly implemented as same as create
		String filename = readVirtualMemoryString(vaddr, 256);
		
		if(filename == null || deleted.containsKey(filename)){
			Lib.debug(dbgProcess, "handleOpen error -- Process is in deletion queue");
			return -1;
		}
		int fd = -1;
		
		// first check if filedescriptor list contains current file, if yes return filedescriptor
		for(int i = 2; i<maxFileDescriptorNum; i++){
			if(descriptorManager.hasFile(i) && descriptorManager.descriptor[i].equals(filename)){
				fd = i;
				break;
			}
		}
		if(fd!=-1)
			return fd;
		
		// open the file
		OpenFile curOpenFile = UserKernel.fileSystem.open(filename, false);

		if(vaddr<0 || curOpenFile == null){
			Lib.debug(dbgProcess, "handleOpen error --Invalid file address openfile falied");
			return -1;
		}
		
		// insert to the available pre-assigned space
		return descriptorManager.addFileToFileDesciptorList(curOpenFile, true);
	}
	
	protected int handleCreate(int vaddr){
		String filename = readVirtualMemoryString(vaddr, 256);
		
		if(filename == null || deleted.containsKey(filename)){
			Lib.debug(dbgProcess, "handleCreat error -- Process is in deletion queue");
			return -1;
		}
		
		// open the file, if not existed, create new
		OpenFile curOpenFile = Machine.stubFileSystem().open(filename, true);
		
		if(curOpenFile == null){
			Lib.debug(dbgProcess, "handleCreat error -- Invalid file address");
			return -1;
		}
		
		return descriptorManager.addFileToFileDesciptorList(curOpenFile, false);
	}
	
	protected int handleJoin(int processID, int status){
		if (!childProcesses.containsKey(processID)){
			Lib.debug(dbgProcess, "handleJoin error --no processID");
			return -1;
		}
		
//		if (!childProcesses.containsKey(processID)){
//			Lib.debug(dbgProcess, "handleJoin error --no processID");
//			return 0;
//		}
		
		childProcesses.get(processID).finished.P();
		byte[] statusBytes = Lib.bytesFromInt(childProcesses.get(processID).status);
		writeVirtualMemory(status, statusBytes);
		
//		UserProcess child = processes.get(processID);
//		childProcesses.get(processID).finished.P();
//		writeVirtualMemory(status, Lib.bytesFromInt(child.status));
//		writeVirtualMemory(status, Lib.bytesFromInt(processes.get(processID).status));
		
//		return child.exitNormally ? 1:0;
		return 1;
	}
	
	protected int readCharPointer(int vaddr){
        byte[] buffer = new byte[4];
        readVirtualMemory(vaddr, buffer);
        return Lib.bytesToInt(buffer, 0);
	}
	
	protected int handleExec(int file, int argc, int argv) {
		String fileName = readVirtualMemoryString(file, 256);
		if(fileName == null || argc < 0){
			Lib.debug(dbgProcess, "handleExec error- invalid name / input argument");
			return -1;
		}
		String[] args = new String[argc];
		// read the argument of input command
		for (int i = 0; i < argc; i++) {
			args[i] = readVirtualMemoryString(readCharPointer(argv + (i*4)), 256);
			if (args[i] == null)
				return -1;
		}	
		// fork new child process
		UserProcess child = newUserProcess();
		// add child PID to current holding list
		childProcesses.put(child.processID, child);
		// execute the process, if cannot return -1
		if (!child.execute(fileName, args)) {
			processes.remove(child.processID);
			return -1;
		}
		return child.processID;	
	}

	protected int handleExit(int status) {
		boolean intstatus = Machine.interrupt().disable();
		unloadSections();
		
		//Any open file descriptor belonging to the process are closed.
		for (int i = 0; i < maxFileDescriptorNum; i++){
			if(descriptorManager.hasFile(i))
				descriptorManager.removeFile(i);
		}
		
		//Release any resources allocated by loadSections().
		
		this.status = status;
		processes.remove(processID);
		
		// finish the process
		finished.V();
		
		// if last proces, terminate kernel
		if (processes.size() == 0)
			Kernel.kernel.terminate();
		
		// schedule next thread
		UThread.finish();
		
		Machine.interrupt().restore(intstatus);
		return 0;
	}
	/**
	 * Handle the halt() system call.
	 */
	protected int handleHalt() {
		if (!this.rootProcess){
			Lib.assertNotReached("not rootProcess");
			return -1;
		}

		Machine.halt(); 
		Lib.assertNotReached("Machine.halt() did not halt machine!");
		return 0;
	}

	/**
	 * Handle a user exception. Called by <tt>UserKernel.exceptionHandler()</tt>
	 * . The <i>cause</i> argument identifies which exception occurred; see the
	 * <tt>Processor.exceptionZZZ</tt> constants.
	 * 
	 * @param cause
	 *            the user exception that occurred.
	 */
	public void handleException(int cause) {
		Processor processor = Machine.processor();

		switch (cause) {
		case Processor.exceptionSyscall:
			int result = handleSyscall(processor.readRegister(Processor.regV0),
					processor.readRegister(Processor.regA0), processor
							.readRegister(Processor.regA1), processor
							.readRegister(Processor.regA2), processor
							.readRegister(Processor.regA3));
			processor.writeRegister(Processor.regV0, result);
			processor.advancePC();
			break;

		default:
			Lib.debug(dbgProcess, "Unexpected exception: "
					+ Processor.exceptionNames[cause]);
			exitNormally = false;
			handleExit(-1);
			return; // XXX
//			Lib.assertNotReached("Unexpected exception");
		}
	}

	/** The program being run by this process. */
	protected Coff coff;

	/** This process's page table. */
	protected TranslationEntry[] pageTable = null;

	/** The number of contiguous pages occupied by the program. */
	protected int numPages;

	/** The number of pages in the program's stack. */
	protected final int stackPages = 8;

	private int initialPC, initialSP;

	private int argc, argv;

	private static final int pageSize = Processor.pageSize;

	private static final char dbgProcess = 'U';
	
	private static final char dbgPeter = 'P';
	
	
}








